#include <string>
#include <vector>
#include <curl/curl.h>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>

///////////////////////////////////////////////////////
// 1. 借助 libcurl 第三方库实现获取网页的功能
// 输入是目标 url
// 输出结果 html
///////////////////////////////////////////////////////
// 这个函数会在 libcurl 接受到响应的时候进行调用.
// 会获取到目标的 html 页面
// ptr: libcurl 接受到的响应的 html 页面的存储缓冲区
// size * nmemb : ptr 缓冲区中数据的长度
// userdata: 用户自定义的一个缓冲区
// 本函数要做的功能, 就是把 ptr 里的数据拷贝到用户自
// 己准备的缓冲区里
size_t  WriteData(char* ptr, size_t size, size_t nmemb, 
    void* userdata) {
  std::string* output = (std::string*)userdata;
  output->append(ptr, size * nmemb);
  return size * nmemb;
}

class ScopedHandler {
public:
  ScopedHandler(CURL* h) : handler(h) {}
  ~ScopedHandler() { curl_easy_cleanup(handler); }
private:
  CURL* handler;
};

bool OpenPage(const std::string& url, std::string* html) {
  // 初始化句柄
  CURL* handler = curl_easy_init();
  ScopedHandler scoped_handler(handler);

  // 构造 HTTP 请求. 最核心的就是设置 URL.
  // HTTP 请求中的其他的 header, libcurl 也是支持的.
  // 当前我们不需要
  curl_easy_setopt(handler, CURLOPT_URL, url.c_str());
  // 设置响应该如何处理
  curl_easy_setopt(handler, CURLOPT_WRITEFUNCTION, WriteData);
  curl_easy_setopt(handler, CURLOPT_WRITEDATA, html);
  // 发送请求
  CURLcode ret = curl_easy_perform(handler);
  if (ret != CURLE_OK) {
    fprintf(stderr, "curl_easy_perform failed!\n");
    return false;
  }
  return true;
}

void Test1() {
  std::string html;
  OpenPage("http://www.shengxu6.com/book/2967.html", &html);
  printf("%s\n", html.c_str());
}

///////////////////////////////////////////////////////
// 2. 把主页中的章节url解析出来
//    完全就是字符串解析的过程
//    借助 正则表达式 基于 boost
///////////////////////////////////////////////////////
void ParseMainPage(const std::string& html,
                   std::vector<std::string>* url_list) {
  boost::regex reg("/read/\\S+html");
  // 借助 reg 对象进行字符串查找
  std::string::const_iterator cur = html.begin();
  std::string::const_iterator end = html.end();
  boost::smatch result;
  while (boost::regex_search(cur, end, result, reg)) {
    // result[0] 对象 boost 中内置的对象. 可以隐式转换成 string
    // 从这个对象中取到 second 属性, 就对应了接下来要查找
    // 的开始位置
    url_list->push_back("http://www.shengxu6.com" + result[0]);
    cur = result[0].second;
  }
}

void Test2() {
  std::string html;
  OpenPage("http://www.shengxu6.com/book/2967.html", &html);
  std::vector<std::string> url_list;
  ParseMainPage(html, &url_list);
  for (size_t i = 0; i < url_list.size(); ++i) {
    printf("%s\n", url_list[i].c_str());
  }
}

///////////////////////////////////////////////////////
// 3. 获取到小说详情页的 html
//    这一步和第一步完全相同. 代码不需要修改
///////////////////////////////////////////////////////
void Test3() {
  std::string html;
  OpenPage("http://www.shengxu6.com/read/2967_2008546.html",
          &html);
  printf("%s\n", html.c_str());
}

///////////////////////////////////////////////////////
// 4. 解析 小说详情页 html, 获取到其中的正文内容
///////////////////////////////////////////////////////
void ParseDetailPage(const std::string& html,
    std::string* content) {
  // 基于字符串查找的方式来进行解析
  // 此处直接使用正则表达式不是特别方便
  // 找到正文的开始位置和结束位置, 然后去取字符串子串即可
  std::string beg_flag = "<div class=\"panel-body content-body content-ext\">";
  size_t beg = html.find(beg_flag);
  if (beg == std::string::npos) {
    fprintf(stderr, "找不到开始标记!\n");
    return;
  }
  beg += beg_flag.size();
  std::string end_flag = "<script>_drgd200();</script>";
  size_t end = html.find(end_flag);
  if (end == std::string::npos) {
    fprintf(stderr, "找不到结束标记!\n");
    return;
  }
  if (beg >= end) {
    fprintf(stderr, "开始结束标记有问题! beg = %lu, end = %lu\n", beg, end);
    return;
  }
  *content = html.substr(beg, end - beg);
  // 替换掉转义字符
  boost::algorithm::replace_all(*content, "&nbsp;", " ");
  boost::algorithm::replace_all(*content, "<br />", "\n");
  return;
}

void Test4() {
  std::string html;
  OpenPage("http://www.shengxu6.com/read/2967_2008546.html",
          &html);
  std::string content;
  ParseDetailPage(html, &content);
  printf("%s\n", content.c_str());
}

///////////////////////////////////////////////////////
// 实现一个总的入口函数把所有的流程串起来
///////////////////////////////////////////////////////
void Run() {
  // 1. 获取到主页的 html
  std::string html;
  OpenPage("http://www.shengxu6.com/book/2967.html", &html);
  // 2. 解析出所有的章节的 url 列表
  std::vector<std::string> url_list;
  ParseMainPage(html, &url_list);
  // 3. 遍历 url 列表, 依次获取到每个章节的 html
  for (size_t i = 0; i < url_list.size(); ++i) {
    fprintf(stderr, "%s\n", url_list[i].c_str());
    OpenPage(url_list[i], &html);
    // 4. 解析每个章节的正文内容
    std::string content;
    ParseDetailPage(html, &content);
    printf("%s\n", content.c_str());
  }
}

int main() {
  // Test1();
  // Test2();
  // Test3();
  // Test4();
  Run();
  return 0;
}
